using System;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class LeftRecursionFeature {
        [Test]
        public void ShouldParseWithLeftRecursiveProductionRightAssociative () {
            var leftExpression = new NamedRule ("left", null);
            var rightExpression = new NamedRule ("right", null);
            IProduction binaryOperator = new NonTerminal ("op", new SequenceRule (leftExpression, new KeywordRule ("+"), rightExpression));
            var expression = new ChoiceProduction (binaryOperator, IntegerProduction.CreateTerminal ());
            leftExpression.Production = expression;
            rightExpression.Production = expression;

            ITerm result = expression.ParseTerm ("1 + 2 + 4");

            Assert.AreEqual ("op {left: 1, right: op {left: 2, right: 4}}", result.ToString ());
        }

        [Test]
        public void ShouldParseWithTwoLeftRecursiveProductions () {
            var leftExpression = new NamedRule ("left", null);
            var rightExpression = new NamedRule ("right", null);
            IProduction plus = new NonTerminal ("plus", new SequenceRule (leftExpression, new KeywordRule ("+"), rightExpression));
            IProduction multiply = new NonTerminal ("multiply", new SequenceRule (leftExpression, new KeywordRule ("*"), rightExpression));
            var expression = new ChoiceProduction (plus, multiply, IntegerProduction.CreateTerminal ());
            leftExpression.Production = expression;
            rightExpression.Production = expression;

            ITerm result = expression.ParseTerm ("1 + 2");

            Assert.AreEqual ("plus {left: 1, right: 2}", result.ToString ());
        }

        [Test]
        public void ShouldParseWithLeftRecursiveProductionLeftAssociative () {
            var leftExpression = new NamedRule ("left", null);
            var rightExpression = new NamedRule ("right", null);
            IProduction binaryOperator = new NonTerminal ("op", new SequenceRule (leftExpression, new KeywordRule ("+"), rightExpression), new CompositeTermBuilder (null), new AssociationSwapper ("left", "right"));
            var expression = new ChoiceProduction (binaryOperator, IntegerProduction.CreateTerminal ());
            leftExpression.Production = expression;
            rightExpression.Production = expression;

            ITerm result = expression.ParseTerm ("1 + 2 + 4");

            Assert.AreEqual ("op {left: op {left: 1, right: 2}, right: 4}", result.ToString ());
        }

        [Test]
        public void ShouldMemoiseGrownParseResult () {
            var leftExpression = new NamedRule ("left", null);
            var rightExpression = new NamedRule ("right", null);
            IProduction binaryOperator = new NonTerminal ("op", new SequenceRule (leftExpression, new KeywordRule ("+"), rightExpression), new CompositeTermBuilder (null), new AssociationSwapper ("left", "right"));
            var expression = new ChoiceProduction (binaryOperator, IntegerProduction.CreateTerminal ());
            leftExpression.Production = expression;
            rightExpression.Production = expression;

            string source = "1 + 2";
            char[] sourceChars = source.ToCharArray ();
            ParseResult result = expression.Parse (sourceChars, 0, new ParseContext (), source,
                                                   new ParseEnvironment ("filename"));

            Assert.AreEqual ("op {left: 1, right: 2}", result.Term.ToString ());
            Assert.That (expression.GetMemoTable (sourceChars)[0], Is.SameAs (result));
        }

        [Test]
        public void ShouldGrowParseForTwoLeftRecursiveProductions () {
            IProduction divideExpression = CreateLeftAssociativeOperator ("divide", "/", IntegerProduction.CreateTerminal ());
            IProduction expression = CreateLeftAssociativeOperator ("minus", "-", divideExpression);

            AssertTermParsed (expression, "1 - 2", "minus {left: 1, right: 2}");
            AssertTermParsed (expression, "1 / 2", "divide {left: 1, right: 2}");
            AssertTermParsed (expression, "1 - 2 / 3", "minus {left: 1, right: divide {left: 2, right: 3}}");
        }

        private static void AssertTermParsed (IProduction expression, string source, string expected) {
            char[] sourceChars = source.ToCharArray ();
            ParseResult result = expression.Parse (sourceChars, 0, new ParseContext (), source,
                                                   new ParseEnvironment ("filename"));

            Assert.AreEqual (expected, result.Term.ToString ());
        }

        private static ChoiceProduction CreateLeftAssociativeOperator (string name, string operatorKeyword, IProduction higherPrecedenceProduction) {
            var minusLeft = new NamedRule ("left", null);
            var minusRight = new NamedRule ("right", higherPrecedenceProduction);
            IProduction minus = new NonTerminal (name, new SequenceRule (minusLeft, new KeywordRule (operatorKeyword), minusRight));
            var expression = new ChoiceProduction (minus, higherPrecedenceProduction);
            minusLeft.Production = expression;
            return expression;
        }

        [Test]
        public void ShouldParseDoubleLeftRecursiveProductions () {
            var expression = new DynamicChoiceProduction ();

            var functionCallRule = new SequenceRule (
                new NamedRule ("function", expression),
                new KeywordRule ("("),
                new DelimitedRule (
                    new NamedRule ("arguments", expression),
                    new KeywordRule (",")),
                new KeywordRule (")"));

            var functionCall = new NonTerminal ("function-call", functionCallRule, new [] {"arguments"});

            IDynamicGrammar grammar = new LazyDynamicGrammar ();
            grammar.AddDynamicChoice (expression, new IProduction [] {functionCall, IdentifierProduction.CreateTerminal ()});

            expression.Name = "expression";

            ITerm term = expression.ParseTerm ("x (a, b) (c)", null, grammar);

            Assert.That (term.ToString (), Is.EqualTo ("function-call {function: function-call {function: 'x', arguments: ['a', 'b']}, arguments: ['c']}"));
        }
    }
}